/**
*
*	my_char_driver.c - Modulo para gestionar un driver de caracteres
*
*/

#include "my_char_driver.h"

/* Licencia del modulo */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alvaro Perez Liaño - Xavier Gallofre Nieva");

/* Contador del numero de veces que se abre el dispositivo */
static int open_count;
/* Tamaño */
static unsigned int count = 1;
/* Estructura dispositivo */
struct my_dev {
	char *buffer;
	int ocupation;
	int size;
} *my_devp;

/* Estructura para gestionar las operaciones */
static struct cdev *my_cdev;
/* Dispositivo */
static dev_t my_device_number;
/* Clase del dispositivo */
struct class *my_class;

/* Operaciones sobre el dispositivo. Definidas en linux/fs.h */
static const struct file_operations mycdrv_fops = {
	.owner = THIS_MODULE,
	.read = my_read,
	.write = my_write,
	.open = my_open,
	.release = my_release,
	.llseek = my_llseek,
	.ioctl = my_ioctl,
};

/* task_struct para el kernel thread */
static struct task_struct *ts;

/* Parametros y variables de entorno de user mode helper */
static char *envp[] = {
	"HOME=/",
	"TERM=linux",
	"PATH=/sbin:/bin:/usr/sbin:/usr/bin",
	NULL 
};

/* ***************************************************** */

/* Funcion del kernel thread */
int kernel_func(void *data) 
{
	printk(KERN_ALERT "Iniciado el kernel thread %s\n", __this_module.name);
	set_current_state(TASK_INTERRUPTIBLE);	
	schedule();
	while (!kthread_should_stop()){ 
		printk(KERN_ALERT "Numero de veces que se ha abierto el dispositivo: %i\n", open_count);
		set_current_state(TASK_INTERRUPTIBLE);	
		schedule();
	}
	printk(KERN_ALERT "Finalizado el kernel thread\n");
	return 0;
}

/* Funcion de apertura del dispositivo */
static int my_open(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "Abriendo dispositivo: %s\n\n", DEV_NAME);
	open_count++;
	/* Notificar al kernel thread que muestre el contador */
	wake_up_process(ts);	
	return 0;
}

/* Funcion de cierre del dispositivo */
static int my_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "Cerrando dispositivo: %s\n\n", DEV_NAME);
	return 0;
}

/* Funcion de lectura del dispositivo */
static ssize_t my_read(struct file *file, char __user * buf, size_t lbuf, loff_t * ppos)
{
	int nbytes;
	nbytes = lbuf - copy_to_user(buf, my_devp->buffer + *ppos, lbuf);
	*ppos += nbytes;
	printk(KERN_ALERT "Callback lectura, nbytes=%d, pos=%d\n", nbytes,(int)*ppos);
	return nbytes;
}

/* Funcion de escritura del dispositivo */
static ssize_t my_write(struct file *file, const char __user * buf, size_t lbuf, loff_t * ppos)
{
	int nbytes;
	nbytes = lbuf - copy_from_user(my_devp->buffer + *ppos, buf, lbuf);
	*ppos += nbytes;
	/* Actualizamos el numero de bytes ocupados */
	my_devp->ocupation = (int)ppos;
	/* Damos formato a la cadena */
	printk(KERN_ALERT "Callback escritura, nbytes=%d, pos=%d\n", nbytes,(int)*ppos);
	return nbytes;
}

/* Funcion de lseek del dispositivo */
static loff_t my_llseek(struct file *file, loff_t offset, int orig)
{ 
	switch (orig) {
		case 0: /* SEEK_SET */
			if (offset <= my_devp->size) {
				file->f_pos = offset;
			} else {
				printk(KERN_ALERT "El offset es mas grande que la cadena\n");
				return -EINVAL;
			}
			break;
			
		case 1: /* SEEK_CUR */
			if ( file->f_pos + offset >= 0 || 
					file->f_pos  + offset <= my_devp->size) {
				file->f_pos = file->f_pos + offset;
				printk(KERN_ALERT "El offset es mas grande que la cadena\n");
				return -EINVAL;
			}
			break;
			
		case 2: /* SEEK_END */
			if ((loff_t)my_devp->ocupation + offset >= 0 ||
					(loff_t)my_devp->ocupation + offset <= my_devp->size) {
				file->f_pos = (loff_t)my_devp->ocupation + offset;
			} else {
				printk(KERN_ALERT "El offset es mas grande que la cadena\n");
				return -EINVAL;
			}
			break;
			
		default:
			return -EINVAL;
	}
	return(file->f_pos);
}

/* Funcion de ioctl del dispositivo */
static int my_ioctl(struct inode *inode, struct file *file, 
	unsigned int cmd, unsigned long arg)
{
	char *temp;
	int copied=0;
	int size=0;

	/* 
	 * Elegimos entre la llamada correspondiente
	 */
	switch (cmd) {
	case IOCTL_RESET:
		file->f_pos = (loff_t)0;
		break;

	case IOCTL_MALLOC:
		/* Guardamos el contenido anterior */
		temp = kmalloc(arg,GFP_KERNEL);
		/* Comprobamos tamaños y copiamos los caracteres antiguos 
		   Copiamos tantos caracteres como quepan, cogemos el menor
		   entre arg y my_devp->ocupation
		*/
		if (my_devp->ocupation > arg) {
			memcpy(temp,my_devp->buffer,arg);
			copied = size = arg;
		} else {
			memcpy(temp,my_devp->buffer,my_devp->ocupation);
			copied = my_devp->ocupation; size = arg;
		}		
		/* Liberamos memoria antigua */
		kfree(my_devp->buffer);
		/* Apuntamos a la variable temp, que ya tiene lo anterior guardado */
		my_devp->buffer = temp;
		/* Ponemos la ocupacion y cambiamos el tamaño */
		my_devp->ocupation = copied;
		my_devp->size = size;
		break;
	
	default:
		printk(KERN_ALERT "Error en IOCTL\n");
		return -EINVAL;
		break;
	}

	return 0;
}

/* ************** INICIO SCRIPTS USER MODE HELPER *************** */
/* Script para comprobar si el fichero de cuenta de instalaciones existe */
static int file_count_exists(void) {
	/* Leemos de un fichero para saber el numero de veces que se ha instalado */
	// Ruta absoluta script
	char *argv[] = {"/home/alvaro/Escritorio/Practica_2/Scripts/script_search.sh", NULL};
	// Comprobamos si existe el fichero
	if (call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC) != 0) {
		printk(KERN_ALERT "No se puede conocer el numero de veces de instalacion\n");
		return -1;
	}
	return 0;
}

/* Suma 1 al numero de veces instalado */
static int update_count(void) {
	/* Ejecutamos un script para sumar valor */
	char *argv[] = {"/home/alvaro/Escritorio/Practica_2/Scripts/script_update.sh", NULL};
	/* Vemos el numero de veces que se ha instalado,
	   si hay un error, salimos
	*/
	if (file_count_exists() != 0) {
		return -1;
	}
	call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
	return 0;
}

/* Mostramos el valor */
static int show_count(void) {
	int num = 0;
	/* Ejecutamos un script de la forma cat fichero para sacar el valor */
	char *argv[] = {"/home/alvaro/Escritorio/Practica_2/Scripts/script_show.sh", NULL};
	/* Vemos el numero de veces que se ha instalado,
	   si hay un error, salimos
	*/
	if (file_count_exists() != 0) {
		return -1;
	}
	if ((num=call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC)) > 0)
		printk(KERN_ALERT "Instalaciones ==> %i\n",  num);
	return 0;
}
	
/* ************** FIN SCRIPTS USER MODE HELPER *************** */

/* Funcion de inicio del dispositivo */
static int __init my_init(void)
{
	/* Añadimos el dispositivo al sistema asignandole el numero major
	 * de manera dinamica */
	if (alloc_chrdev_region(&my_device_number, 0, count, DEV_NAME) < 0) {
		printk(KERN_ALERT "No puede registrarse el dispositivo\n"); return -1;
	}
	/* Creamos la clase */
	my_class = class_create(THIS_MODULE, DEV_NAME);
	/* Iniciamos las operaciones */
	my_cdev = cdev_alloc();
	cdev_init(my_cdev, &mycdrv_fops);
	/* Asociamos las operaciones al dispositivo */
	cdev_add(my_cdev, my_device_number, count);
	/* Añadimos el dispositivo final */
	device_create(my_class, NULL, my_device_number, NULL, "my_char_device");	
	printk(KERN_ALERT "\n Se ha registrado el dispostivo de caracteres %s\n", DEV_NAME);
	
	/* *********** Inicializamos nuestra estructura ************ */
	my_devp = kmalloc(sizeof(struct my_dev), GFP_KERNEL);
	/* Reserva memoria buffer */
	my_devp->buffer = kmalloc(DISC_SIZE, GFP_KERNEL);
	my_devp->ocupation = 0;
	my_devp->size = DISC_SIZE;
	
	/* *********** Kernel thread ************ */
	/* Inciamos el contador de veces que se ha abierto el dispositivo */
	open_count = 0;
	/* Creamos el kernel thread */
  	ts = kthread_run(kernel_func,NULL,"My_Thread");
  	
	/* *********** Ejecutamos Script ********* */
	/* Actualizamos el fichero de cuenta de instalaciones */
	update_count();
	/* Mostramos el numero de instalaciones */
	show_count();
	return 0;
}

/* Funcion de salida del dispositivo */
static void __exit my_exit(void)
{
	/* Eliminamos las operaciones asociadas */
	cdev_del(my_cdev);
	unregister_chrdev_region(MAJOR(my_device_number), count);
	/* Eliminamos el dispositivo */
	device_destroy(my_class, MKDEV(MAJOR(my_device_number),MINOR(my_device_number)));
	/* Eliminamos la clase */
	class_destroy(my_class);
	/* Liberamos memoria de la estructura */
	kfree(my_devp->buffer);
	kfree(my_devp);
	/* Eliminamos el kernel thread */
	kthread_stop(ts);
	printk(KERN_ALERT "\nLiberado registro de caracteres\n");
}

module_init(my_init);
module_exit(my_exit);
